package com.kevin.util;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @类名: HttpAsyncUtil
 * @包名：com.kevin.util
 * @时间：2018/5/4 11:03
 * @版本：1.0
 * @描述：异步请求适用于与用户无关的批量操作，比如发短信，或者请求第三方等。在全部调用完毕后，请执行close()关闭长连接
 */
public class HttpAsyncUtil {

    private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);

    /**
     * 连接池最大连接数
     **/
    private static final int HTTPCLIENT_MAX_TOTAL = 200;
    /**
     * 单个路由最大连接数
     **/
    private static final int HTTPCLIENT_DEFAULT_MAX_PER_ROUTE = 20;
    /**
     * 从连接管理器获取连接的超时时间
     **/
    private static final int HTTPCLIENT_CONNECTION_REQUEST_TIMEOUT = 1000;
    /**
     * 建立连接的超时时间
     **/
    private static final int HTTPCLIENT_CONNECT_TIMEOUT = 5000;
    /**
     * 请求获取数据的超时时间
     **/
    private static final int HTTPCLIENT_SOCKET_TIMEOUT = 300000;

    // 连接管理器
    private static PoolingNHttpClientConnectionManager connectionManager;
    // 请求配置
    private static RequestConfig requestConfig;
    // http异步客户端构建器
    private static HttpAsyncClientBuilder httpAsyncClientBuilder;
    // http异步客户端
    private static CloseableHttpAsyncClient httpAsyncClient;
    // http异步工具类实例
    private static final HttpAsyncUtil httpAsyncUtil = new HttpAsyncUtil();

    static {
        try {
            // 创建http连接管理器，使用连接池管理http连接
            connectionManager = new PoolingNHttpClientConnectionManager(new DefaultConnectingIOReactor());
            // 设置最大连接数
            connectionManager.setMaxTotal(HTTPCLIENT_MAX_TOTAL);
            // 设置每个路由的最大连接数
            connectionManager.setDefaultMaxPerRoute(HTTPCLIENT_DEFAULT_MAX_PER_ROUTE);
            // 创建请求配置
            requestConfig = RequestConfig.custom()
                    .setConnectionRequestTimeout(HTTPCLIENT_CONNECTION_REQUEST_TIMEOUT)
                    .setConnectTimeout(HTTPCLIENT_CONNECT_TIMEOUT)
                    .setSocketTimeout(HTTPCLIENT_SOCKET_TIMEOUT)
                    .build();
            // 创建http客户端构建器
            httpAsyncClientBuilder = HttpAsyncClients.custom()
                    .setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(requestConfig);
        } catch (IOReactorException e) {
            e.printStackTrace();
        }
    }

    private HttpAsyncUtil() {
        // no constructor function
    }

    /**
     * 获取HttpAsyncUtil单例，便于后续调用close()方法关闭长连接
     * @return
     */
    public static HttpAsyncUtil getInstance() {
        return httpAsyncUtil;
    }

    /**
     * 启动客户端
     */
    private static void start() {
        if (httpAsyncClient == null) {
            httpAsyncClient = httpAsyncClientBuilder.build();
        } else if (httpAsyncClient.isRunning()) {
            return;
        }
        httpAsyncClient.start();
    }

    /**
     * 关闭客户端
     */
    public static void close() {
        try {
            if (httpAsyncClient != null && httpAsyncClient.isRunning()) {
                httpAsyncClient.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * get请求
     * @param url           请求url
     * @param callback      回调函数
     */
    public static void get(String url, FutureCallback<HttpResponse> callback) {
        if (StringUtil.isEmpty(url)) {
            log.error("GET请求不合法，请检查uri参数!");
            return;
        }

        // 创建GET请求
        HttpGet httpGet = new HttpGet(url);
        // 执行GET请求
        execute(httpGet, callback);
    }

    /**
     * post请求
     * @param url       请求url
     * @param jsonStr   json格式的请求参数
     * @param callback  回调函数
     */
    public void post(String url, String jsonStr, FutureCallback<HttpResponse> callback) {
        if (StringUtil.isEmpty(url)) {
            log.error("POST请求不合法，请检查uri参数!");
            return;
        }

        // 创建POST请求
        HttpPost httpPost = new HttpPost(url);
        // 设置POST请求
        StringEntity entity = new StringEntity(jsonStr, Charset.forName("UTF-8"));
        entity.setContentType("application/json");
        entity.setContentEncoding("UTF-8");
        httpPost.setEntity(entity);
        // 执行POST请求
        execute(httpPost, callback);
    }

    /**
     * post请求
     * @param url       请求url
     * @param params    表单形式的请求参数
     * @param callback  回调函数
     */
    public void post(String url,  List<NameValuePair> params, FutureCallback<HttpResponse> callback) {
        if (StringUtil.isEmpty(url)) {
            log.error("POST请求不合法，请检查uri参数!");
            return;
        }

        // 创建POST请求
        HttpPost httpPost = new HttpPost(url);
        // 设置POST请求
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, Charset.forName("UTF-8"));
        entity.setContentType("application/x-www-form-urlencoded");
        entity.setContentEncoding("UTF-8");
        // 执行POST请求
        execute(httpPost, callback);
    }

    /**
     * 执行请求
     * @param request
     * @param callback
     */
    private static void execute(HttpUriRequest request, FutureCallback<HttpResponse> callback) {
        start();
        httpAsyncClient.execute(request, callback);
    }

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(2);
        FutureCallback<HttpResponse> callback = new FutureCallback<HttpResponse>() {
            @Override
            public void completed(HttpResponse response) {
                countDownLatch.countDown();
                System.out.println("callback thread id is : " + Thread.currentThread().getId());
                try {
                    String content = EntityUtils.toString(response.getEntity(), "UTF-8");
                    System.out.println("response content is: " + content);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Exception ex) {
                countDownLatch.countDown();;
                System.out.println(" callback thread id is : " + Thread.currentThread().getId());
            }

            @Override
            public void cancelled() {
                countDownLatch.countDown();
                System.out.println(" callback thread id is : " + Thread.currentThread().getId());
            }
        };

        final HttpGet httpGet = new HttpGet("http://www.sohu.com");
        final HttpGet httpsGet = new HttpGet("https://www.hao123.com");
        final HttpPost httpPost = new HttpPost("http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0");
        final HttpPost httpsPost = new HttpPost("https://www.hao123.com");
        HttpAsyncUtil.execute(httpGet,callback);
        HttpAsyncUtil.execute(httpPost,callback);
        HttpAsyncUtil.execute(httpsGet,callback);
        HttpAsyncUtil.execute(httpsPost,callback);

        countDownLatch.await();
        HttpAsyncUtil.close();
    }
}
